package networkLoader;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.stream.Collectors;

import eval.Checker;
import jdd.bdd.BDD;
import network.Device;
import network.Network;
import network.Port;
import network.Rule;
import verifier.APKeepVerifier;
import verifier.Ports.ArrayPorts;
import verifier.util.Changes;

public class Dv {

    public static <T> T random(Collection<T> coll) {
        int num = (int) (Math.random() * coll.size());
        for(T t: coll) if (--num < 0) return t;
        throw new AssertionError();
    }

    public static void steval() {
        Network n = StanfordNetwork.getNetwork();
        // System.out.println(n.getAllDevices().size());
        // System.out.println(n.getInitialRules().size());
        long avg = 0;
        int cnt = 4;
        for (int i = 0; i < cnt; i++) {
            long t = -System.nanoTime();
            APKeepVerifier apkeep = new APKeepVerifier(n, new ArrayPorts());
            for (Rule rule : n.getInitialRules()) {
                apkeep.insertRule(new Rule(rule.getDevice(), rule.getMatch().longValue(), rule.getPrefix(), rule.getOutPort()));
                apkeep.update(true);
            }
            System.out.println(apkeep.predSize());
            double s = Checker.allPair(n, apkeep.getPortToPreds());
            t += System.nanoTime();
            avg += t;
            System.out.println(String.format("total: %s, check: %s", t, s));
        }
        System.out.println("avg total: " + avg/cnt);
    }

    public static void inceval(Network n) {
        // Network n = StanfordNetwork.getNetwork();
        // System.out.println(n.getAllDevices().size());
        // System.out.println(n.getInitialRules().size());
        long avg = 0;
        int cnt = 1004;
        
        long st = -System.nanoTime();
        APKeepVerifier apkeep = new APKeepVerifier(n, new ArrayPorts());
        // for (Rule rule : n.getInitialRules()) {
        //     apkeep.insertRule(new Rule(rule.getDevice(), rule.getMatch().longValue(), rule.getPrefix(), rule.getOutPort()));
        //     apkeep.update(true);
        // }
        // long s = Checker.allPair(n, apkeep.getPortToPreds());
        st += System.nanoTime();
        // System.out.println(String.format("total: %s, check: %s", st, s));
        // System.out.println(apkeep.predSize());
        // System.out.println(String.format("total: %s, check: %s", t, s));
        for (int i = 0; i < cnt; i++) {
            Boolean modify = false;
            Rule rr = random(n.getInitialRules());

            long t = -System.nanoTime();
            List<Integer> changes1 = apkeep.removeRule(rr).stream().map(c -> c.bdd).collect(Collectors.toList());
            apkeep.update(true);
            if (Math.random() < 0.5) { // randomly modify
                modify = true;
                Port rp = random(rr.getDevice().getPorts());
                Rule nr = new Rule(rr.getDevice(), rr.getMatch().longValue(), rr.getPrefix(), rp);
                List<Integer> changes2 = apkeep.insertRule(nr).stream().map(c -> c.bdd).collect(Collectors.toList());
                apkeep.update(true);
                changes1.addAll(changes2);
                n.getInitialRules().add(nr);
            }
            Checker.allPairInc(n, apkeep.getPortToPreds(), new HashSet<>(changes1));
            t += System.nanoTime();
            avg += t;
            n.getInitialRules().remove(rr);
            if (i > 3) {
                if (modify) {
                    System.out.println("mod " + t);
                } else {
                    System.out.println("del " + t);
                }
            }
        }
            
        // System.out.println("avg total: " + avg/cnt);
    }

    public static void i2eval() {
        Network n = I2Network.getNetwork();
        // System.out.println(n.getAllDevices().size());
        // System.out.println(n.getInitialRules().size());
        long avg = 0;
        int cnt = 4;
        for (int i = 0; i < cnt; i++) {
            long t = -System.nanoTime();
            APKeepVerifier apkeep = new APKeepVerifier(n, new ArrayPorts());
            for (Rule rule : n.getInitialRules()) {
                apkeep.insertRule(new Rule(rule.getDevice(), rule.getMatch().longValue(), rule.getPrefix(), rule.getOutPort()));
                apkeep.update(true);
            }
            System.out.println(apkeep.predSize());
            double s = Checker.allPair(n, apkeep.getPortToPreds());
            t += System.nanoTime();
            avg += t;
            System.out.println(String.format("total: %s, check: %s", t, s));
        }
        System.out.println("avg total: " + avg/cnt);
    }

    public static void fattreeeval() {
        Network n = FattreeNetwork.getNetwork(48);
        // System.out.println(n.getAllDevices().size());
        // System.out.println(n.getInitialRules().size());
        long avg = 0;
        int cnt = 4;
        for (int i = 0; i < cnt; i++) {
            long t = -System.nanoTime();
            APKeepVerifier apkeep = new APKeepVerifier(n, new ArrayPorts());
            for (Rule rule : n.getInitialRules()) {
                apkeep.insertRule(new Rule(rule.getDevice(), rule.getMatch().longValue(), rule.getPrefix(), rule.getOutPort()));
                apkeep.update(true);
            }
            System.out.println(apkeep.predSize());
            double s = Checker.allPair(n, apkeep.getPortToPreds());
            t += System.nanoTime();
            avg += t;
            System.out.println(String.format("total: %s, check: %s", t, s));
        }
        System.out.println("avg total: " + avg/cnt);
    }

    public static void fbeval() {
        Network n = FBNetwork.getNetwork();
        // System.out.println(n.getAllDevices().size());
        // System.out.println(n.getInitialRules().size());
        long avg = 0;
        int cnt = 4;
        for (int i = 0; i < cnt; i++) {
            long t = -System.nanoTime();
            APKeepVerifier apkeep = new APKeepVerifier(n, new ArrayPorts());
            for (Rule rule : n.getInitialRules()) {
                apkeep.insertRule(new Rule(rule.getDevice(), rule.getMatch().longValue(), rule.getPrefix(), rule.getOutPort()));
                apkeep.update(true);
            }
            System.out.println(apkeep.predSize());
            double s = Checker.allPair(n, apkeep.getPortToPreds());
            t += System.nanoTime();
            avg += t;
            System.out.println(String.format("total: %s, check: %s", t, s));
        }
        System.out.println("avg total: " + avg/cnt);
    }

    public static void evalLEC(Network n) {
        for (int i = 0; i < 1004; i++) {
            Device rdev = random(n.getAllDevices().stream().filter(d -> d.getName().startsWith("rsw") || d.getName().startsWith("fsw")).collect(Collectors.toList()));
            APKeepVerifier apkeep = new APKeepVerifier(n, new ArrayPorts());
            List<Rule> rules = n.getInitialRules().stream().filter(r -> r.getDevice() == rdev).toList();
            for (Rule rule : rules) {
                apkeep.insertRule(rule);
                apkeep.changes.clear();
            }
            HashMap<Port, Integer> lec = new HashMap<>();
            for (Port p : rdev.getPorts()) {
                lec.put(p, 0);
            }
            Map<Port, List<Rule>> grouped = rules.stream().collect(Collectors.groupingBy(Rule::getOutPort));
            for (Entry<Port, List<Rule>> entrySet : grouped.entrySet()) {
                int ec = 0;
                for (Rule r : entrySet.getValue()) {
                    int t = apkeep.bddEngine.or(ec, r.getHit());
                    apkeep.bddEngine.deRef(ec);
                    ec = t;
                }
                lec.put(entrySet.getKey(), ec);
            }
            Rule rr = random(rules);
            long time = - System.nanoTime();
            List<APKeepVerifier.Change> changes1 = new ArrayList<>(apkeep.removeRule(rr));
            apkeep.changes.clear();
            if (Math.random() < 0.5) { // randomly modify
                // modify = true;
                Port rp = random(rr.getDevice().getPorts());
                Rule nr = new Rule(rr.getDevice(), rr.getMatch().longValue(), rr.getPrefix(), rp);
                List<APKeepVerifier.Change> changes2 = new ArrayList<>(apkeep.insertRule(nr));
                apkeep.changes.clear();
                // apkeep.update(true);
                changes1.addAll(changes2);
                // n.getInitialRules().add(nr);
            }
            // System.out.println(changes1.size());
            for (APKeepVerifier.Change change : changes1) {
                
                lec.put(change.oldPort, apkeep.bddEngine.and(lec.get(change.oldPort), apkeep.bddEngine.not(change.bdd)));
                lec.put(change.newPort, apkeep.bddEngine.or(lec.get(change.newPort), change.bdd));
            }
            time += System.nanoTime();
            if (i > 3) {
                System.out.println(time);
            }
            System.gc();
        }
    }

    public static void main(String[] args) {
        evalLEC(FBNetwork.getNetwork());
    }
    
}
